package point;

import java.awt.Dimension;
import environment.Simulation;
import force.CenterMass;
import force.ForceVector;
import force.Gravity;
import force.Viscosity;
import force.WallRepulsion;


/**
 * @author Xi Du, Michael Deng
 */
public class Mass extends ObjectPoint {
    /**
     * @param ZERO zero
     */
    private static final int ZERO = 0;

    /**
     * @param HALFANGLE 180 degree
     */
    private static final int HALFANGLE = 180;

    /**
     * @param QUARTERSANGLE 270 degree
     */
    private static final int QUARTERSANGLE = 270;

    /**
     * @param QUARTERANGLE 90 degree
     */
    private static final int QUARTERANGLE = 90;

    /**
     * @param gravity new gravity force receive from simulation
     */
    private Gravity gravity = new Gravity(0, 0);
    /**
     * @param viscosity new viscosity force receive from simulation
     */
    private Viscosity viscosity = new Viscosity(0, 0);
    /**
     * @param centermass new center mass force receive from simulation
     */
    private CenterMass centermass = new CenterMass(0, 0);
    /**
     * @param wallrepulsion wall repulsion force receive from simulation
     */
    private WallRepulsion wallrepulsion = new WallRepulsion(0, 0, 0);

    /**
     * @param g set gravity force
     */
    public final void setGravity(final Gravity g) {
        gravity = g;
    }

    /**
     * @param v set viscosity force
     */
    public final void setViscosity(final Viscosity v) {

        viscosity = v;
    }

    /**
     * @param c set center mass force
     */
    public final void setCentermass(final CenterMass c) {
        centermass = c;
    }

    /**
     * @param w set wall repulsion force
     */
    public final void setWallrepulsion(final WallRepulsion w) {
        wallrepulsion = w;

    }

    /**
     * @param BOUNCE_MAGNITUDE the magnitude of bouncing
     */
    private static final int BOUNCE_MAGNITUDE = 2;

    // state
    /**
     * @param myVelocity
     */
    private ForceVector myVelocity;

    /**
     * @param id id of masses
     * @param x location x
     * @param y location y
     * @param mass mass of the point
     */
    public Mass(final int id, final double x,
            final double y, final double mass) {
        // use the points constructor
        super(id, x, y, mass);
        setVelocity(0, 0);

    }

    /**
     * @param gravityisRunning determine if apply gravity force
     */
    private static Boolean gravityisRunning = true;

    /**
     * 
     * @param b set gravityisRunning
     */
    public static void setgravityisRunning(final Boolean b) {
        gravityisRunning = b;
    }

    /**
     * 
     * @return gravityisRunning
     */
    public static Boolean getgravityisRunning() {
        return gravityisRunning;
    }

    /**
     * @param viscosityisrunning determine if apply viscosity force
     */
    private static Boolean viscosityisrunning = true;

    /**
     * 
     * @param b set viscosityisrunning
     */
    public static void setviscosityisrunning(final Boolean b) {
        viscosityisrunning = b;
    }

    /**
     * 
     * @return viscosityisrunning
     */
    public static Boolean getviscosityisrunning() {
        return viscosityisrunning;
    }

    /**
     * @param centermassisrunning determine if apply center mass force
     */
    private static Boolean centermassisrunning = true;

    /**
     * 
     * @param b set centermassisrunning
     */
    public static void setcentermassisrunning(final Boolean b) {
        centermassisrunning = b;
    }

    /**
     * 
     * @return get centermassisrunning
     */
    public static Boolean getcentermassisrunning() {
        return centermassisrunning;
    }

    /**
     * @param rightwallrepulsionisrunning determine
     *        if apply right wall repulsion force
     */
    private static Boolean rightwallrepulsionisrunning = true;
    /**
     * @param upwallrepulsionisrunning determine
     *        if apply up wall repulsion force
     */
    private static Boolean upwallrepulsionisrunning = true;
    /**
     * @param leftwallrepulsionisrunning determine
     *        if apply left wall repulsion force
     */
    private static Boolean leftwallrepulsionisrunning = true;
    /**
     * @param bottomwallrepulsionisrunning determine
     *        if apply bottom wall repulsion force
     */
    private static Boolean bottomwallrepulsionisrunning = true;

    /**
     * 
     * @param b set rightwallrepulsionisrunning
     */
    public static void setrightwallrepulsionisrunning(final Boolean b) {
        rightwallrepulsionisrunning = b;
    }

    /**
     * 
     * @return rightwallrepulsionisrunning
     */
    public static Boolean getrightwallrepulsionisrunning() {
        return rightwallrepulsionisrunning;
    }

    /**
     * 
     * @param b set upwallrepulsionisrunning
     */
    public static void setupwallrepulsionisrunning(final Boolean b) {
        upwallrepulsionisrunning = b;
    }

    /**
     * 
     * @return upwallrepulsionisrunning
     */
    public static Boolean getupwallrepulsionisrunning() {
        return upwallrepulsionisrunning;
    }

    /**
     * 
     * @param b set leftwallrepulsionisrunning
     */
    public static void setleftwallrepulsionisrunning(final Boolean b) {
        leftwallrepulsionisrunning = b;
    }

    /**
     * 
     * @return leftwallrepulsionisrunning
     */
    public static Boolean getleftwallrepulsionisrunning() {
        return leftwallrepulsionisrunning;
    }

    /**
     * 
     * @param b set bottomwallrepulsionisrunning
     */
    public static void setbottomwallrepulsionisrunning(final Boolean b) {
        bottomwallrepulsionisrunning = b;
    }

    /**
     * 
     * @return bottomwallrepulsionisrunning
     */
    public static Boolean getbottomwallrepulsionisrunning() {
        return bottomwallrepulsionisrunning;
    }

    /**
     * @param canvas container
     * @param dt time
     */
    public final void update(final Simulation canvas, final double dt) {
        Viscosity v;
        ForceVector c;

        /*if gravityisRunning is true, the mass can apply gravity force,
         * gravityisRunning can be changed through setter */
        if (gravityisRunning) {
            applyForce(gravity);
        }

        //get the current Velocity angle
        double angle = myVelocity.getmyAngle();

        /*since the viscosity force has the inverse direction,
         we need to turn the current velocity direction*/
        angle += HALFANGLE;

        //viscosityisrunning is the indicator of viscosity force
        if (viscosityisrunning) {
            //the viscosity force's magnitude is proportional to velocity
            v = new Viscosity(angle, viscosity.getmyScale()
                    * Math.abs(myVelocity.getXChange()));
             applyForce(v);
        }
        //recover the velocity's direction
        angle -= HALFANGLE;

        //calculate the center mass angle and distance
        double centermassAngle = ForceVector.angleBetween(canvas.getxCenter()
                - getCenter().getX(), canvas.getyCenter() - getCenter().getY());
        double centermassDistance = ForceVector.distanceBetween(canvas.getxCenter()
                - getCenter().getX(), canvas.getyCenter() - getCenter().getY());

        if (centermassisrunning) {
            //apply canter force
            c = new ForceVector(centermassAngle, centermass.getMagnitude()
                    / Math.pow(centermassDistance, centermass.getmyExp()));
            applyForce(c);
        }

        switch (wallrepulsion.getmyDirection()) {
            //apply four direction wall repuslison force
            case 0:
                if (rightwallrepulsionisrunning) {
                    //calculate the distance to the left wall
                    wallrepulsion.setdistance(getCenter().getX()
                            + myBoundschange);
                    applyForce(wallrepulsion);
                }
            case QUARTERANGLE:
                if (upwallrepulsionisrunning) {
                  //calculate the distance to the bottom wall
                    wallrepulsion.setdistance(getCenter().getY()
                            + myBoundschange);
                    applyForce(wallrepulsion);
                }
            case HALFANGLE:
                if (leftwallrepulsionisrunning) {
                  //calculate the distance to the right wall
                    wallrepulsion.setdistance(canvas.getSize().getWidth()
                            - getCenter().getX() - myBoundschange);
                    applyForce(wallrepulsion);
                }
            case QUARTERSANGLE:
                if (bottomwallrepulsionisrunning) {
                  //calculate the distance to the up wall
                    wallrepulsion.setdistance(canvas.getSize().getHeight()
                            - getCenter().getY() - myBoundschange);
                    applyForce(wallrepulsion);
                }
            default:
                //if no the above cases, don't apply force
                wallrepulsion.setdistance(1);
        }

        applyForce(getBounce(canvas.getSize()));

        // convert force back into Mover's velocity
        myVelocity.sum(getAccel());

        getAccel().reset();
        // move mass by velocity
        getCenter().setLocation(
                getCenter().getX() + myVelocity.getXChange() * dt,
                getCenter().getY() + myVelocity.getYChange() * dt);
    }

    /**
     * 
     * @param f apply gravity
     */
    public final void applyForce(final Gravity f) {
        getAccel().sum(f);
    };

    /**
     * @param f force to be applied to this mass
     */
    public final void applyForce(final ForceVector f) {
        getAccel().sum(f);
    }


    /**
     * @param myBoundschange indicate the bounds changes
     */
    private static int myBoundschange = 0;

    /**
     * 
     * @param a set bounds change
     */
    public static void setmyBoundschange(final int a) {
        myBoundschange += a;
    }

    /**
     * @param bounds bounds of the canvas
     * @return force that pull it back
     */
    private ForceVector getBounce(final Dimension bounds) {
        ForceVector impulse = new ForceVector();
        if (getLeft() < ZERO + myBoundschange) {
            impulse = new ForceVector(0, BOUNCE_MAGNITUDE);
            setCenter(getSize().width / 2 + myBoundschange, getCenter().getY());
        }
        else if (getRight() > bounds.width + myBoundschange) {
            impulse = new ForceVector(HALFANGLE, BOUNCE_MAGNITUDE);
            setCenter(bounds.width - getSize().width / 2 + myBoundschange,
                    getCenter().getY());
        }
        if (getTop() < ZERO + myBoundschange) {
            impulse = new ForceVector(QUARTERSANGLE, BOUNCE_MAGNITUDE);
            setCenter(getCenter().getX(),
                    getSize().height / 2 + myBoundschange);
        }
        else if (getBottom() > bounds.height + myBoundschange) {
            impulse = new ForceVector(QUARTERANGLE, BOUNCE_MAGNITUDE);
            setCenter(getCenter().getX(), bounds.height - getSize().height / 2
                    + myBoundschange);
        }
        impulse.scale(myVelocity.getRelativeMagnitude(impulse));
        return impulse;
    }
    /**
     * @param direction set according to the given direction
     * @param magnitude the magnitude of the force
     *        Resets shape's velocity.
     */
    public final void setVelocity(final double direction,
            final double magnitude) {
        myVelocity = new ForceVector(direction, magnitude);
    }
}
